﻿/****************************************************************************************
 * Name         :   3D Image Processor
 * Author       :   Shah-nawaj Shuhan
 * Description  :   The application uses two cameras to capture images, Cameras should be
 *                  aligned horizentally. Camera seperation should be around 3 inch. The
 *                  application captures two 2D images and combines them to form Red Cyan
 *                  sterioscopic 3D image.
 *                  
 * References   :   AForge.NET is used to capture Image from webcam using Directshow
 *                  Unsafe pixel access: http://www.dreamincode.net/forums/topic/14788-c%23-fast-acces-to-bitmap-pixels/
 ****************************************************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using AForge.Video;
using AForge.Video.DirectShow;

namespace _3D_Processor
{
    public partial class MainForm : Form
    {
        // list of video devices
        FilterInfoCollection videoDevices;

        private bool isReady = false;

        private string SoundPath;

        private System.Media.SoundPlayer WavPlayer;

        public MainForm()
        {
            InitializeComponent();

            SoundPath = Application.StartupPath;
            if (SoundPath[SoundPath.Length - 1] != '\\')
            {
                SoundPath += "\\";
            }
            SoundPath += "sound\\camsound.wav";
            WavPlayer = new System.Media.SoundPlayer(SoundPath);
            // show device list
            try
            {
                // enumerate video devices
                videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);

                if (videoDevices.Count == 0)
                {
                    throw new Exception();
                }

                for (int i = 1, n = videoDevices.Count; i <= n; i++)
                {
                    string cameraName = i + " : " + videoDevices[i - 1].Name;

                    LeftCamCombo.Items.Add(cameraName);
                    RightCamCombo.Items.Add(cameraName);
                }

                // check cameras count
                if (videoDevices.Count == 1)
                {
                    RightCamCombo.Items.Clear();
                    isReady = false;
                    RightCamCombo.Items.Add("Only one camera found");
                    RightCamCombo.SelectedIndex = 0;
                    RightCamCombo.Enabled = false;
                }
                else
                {
                    RightCamCombo.SelectedIndex = 1;
                    isReady = true;
                }
                LeftCamCombo.SelectedIndex = 0;
            }
            catch
            {
                isReady = false;
                CaptureImage.Enabled = false;

                LeftCamCombo.Items.Add("No cameras found");
                RightCamCombo.Items.Add("No cameras found");

                LeftCamCombo.SelectedIndex = 0;
                RightCamCombo.SelectedIndex = 0;

                LeftCamCombo.Enabled = false;
                RightCamCombo.Enabled = false;
            }
            finally
            {
                if (isReady)
                {
                    StartCameras();
                    LeftCamCombo.SelectedIndexChanged += new EventHandler(CamCombo_SelectedIndexChanged);
                    RightCamCombo.SelectedIndexChanged += new EventHandler(CamCombo_SelectedIndexChanged);
                }
                else
                {
                    MessageBox.Show("Sorry, your system is not ready for 3D capturing.\n You must have 2 cameras installed.\n Please check your camera connections and try again", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                    Application.Exit();
                }
            }
        }

        // Start cameras
        private void StartCameras()
        {
            // create first video source
            VideoCaptureDevice videoSource1 = new VideoCaptureDevice(videoDevices[LeftCamCombo.SelectedIndex].MonikerString);
            videoSource1.DesiredFrameRate = 10;

            LeftCamWindow.VideoSource = videoSource1;
            LeftCamWindow.Start();

            // create second video source
            if (RightCamCombo.Enabled == true)
            {
                System.Threading.Thread.Sleep(500);

                VideoCaptureDevice videoSource2 = new VideoCaptureDevice(videoDevices[RightCamCombo.SelectedIndex].MonikerString);
                videoSource2.DesiredFrameRate = 10;

                RightCamWindow.VideoSource = videoSource2;
                RightCamWindow.Start();
            }
            if (RightCamCombo.SelectedIndex == LeftCamCombo.SelectedIndex)
            {
                CaptureImage.Enabled = false;
                Errl.Text = "Camera conflicted (Please select two different camera)";
            }
            else
            {
                Thread.Sleep(1000);
                CaptureImage.Enabled = true;
                Errl.Text = "";
            }
        }

        // Stop cameras
        private void StopCameras()
        {
           LeftCamWindow.SignalToStop();
           RightCamWindow.SignalToStop();

           LeftCamWindow.WaitForStop();
           RightCamWindow.WaitForStop();

           CaptureImage.Enabled = false;
        }

        void Hold()
        {
            //Browse.Enabled = false;
            //Normalb.Enabled = false;
            //RedFilterb.Enabled = false;
            //CyanFilterb.Enabled = false;
            foreach (Control b in this.Controls)
            {
                b.Enabled = false;
            }
        }

        void ok()
        {
            //Browse.Enabled = true;
            //Normalb.Enabled = true;
            //RedFilterb.Enabled = true;
            //CyanFilterb.Enabled = true;
            foreach (Control b in this.Controls)
            {
                b.Enabled = true;
            }
        }

        #region Image Capturing Subroutine

        CaptureModes CaptureMode;

        /// <summary>
        /// Capture an image after a fixed period of time
        /// </summary>
        /// <param name="Seconds"></param>
        private void TimerCapture(Object Second)
        {
            int Seconds = (int)Second;
            IntHandler CountDownUpdater = new IntHandler(UpdateCountDown);
            IntHandler UpdatePC = new IntHandler(ProcessCompleted);

            while (Seconds > 0)
            {
                if (Seconds < 6 && Seconds > 0)
                {
                    System.Media.SystemSounds.Beep.Play();
                }
                if (isCancle) { this.Invoke(UpdatePC, new Object[] { 1 }); return; }
                this.Invoke(CountDownUpdater, new Object[] { Seconds });
                Thread.Sleep(1000);
                Seconds--;
            }
            this.Invoke(CountDownUpdater, new Object[] { Seconds });
            Capture();
            this.Invoke(UpdatePC, new Object[] { 1 });
        }

        private void UpdateCountDown(int i)
        {
            CountDownPan.Visible = true;
            CountDownLbl.Text = i.ToString();
        }

        private void ProcessCompleted(int i)
        {
            CaptureImage.Text = "Capture";
            CountDownPan.Visible = false;
        }
        /// <summary>
        /// Capture image sequence
        /// </summary>
        /// <param name="Params"></param>
        private void SequenceCapture(Object Params)
        {
            Object[] Objs = (Object[])Params;
            int Intervel = (int)Objs[0];
            int Duration = (int)Objs[1];
            IntHandler CountDownUpdater = new IntHandler(UpdateCountDown);
            IntHandler UpdatePC = new IntHandler(ProcessCompleted);

            Capture();
            while (Duration >= Intervel)
            {
                int Seconds = Intervel;
                while (Seconds > 0)
                {
                    if (Seconds < 6 && Seconds > 0)
                    {
                        System.Media.SystemSounds.Beep.Play();
                    }
                    if (isCancle) { this.Invoke(UpdatePC, new Object[] { 1 }); return; }
                    this.Invoke(CountDownUpdater, new Object[] { Duration });
                    Thread.Sleep(1000);
                    Seconds--;
                    Duration--;
                }
                this.Invoke(CountDownUpdater, new Object[] { Duration });
                Capture();
            }
            this.Invoke(UpdatePC, new Object[] { 1 });
        }

        /// <summary>
        /// Capture an Image
        /// </summary>
        /// <returns></returns>
        new private bool Capture()
        {
            StringHandler StatusUpdate = new StringHandler(UpdateStatus);
            Bitmap lbmp = LeftCamWindow.GetCurrentVideoFrame();
            Bitmap rbmp = RightCamWindow.GetCurrentVideoFrame();

            //To eleminate the error when the left or right camera has stoped
            if (lbmp == null || rbmp == null)
            {
                MessageBox.Show("One or more camera has stoped working. Please select two active cameras to capture.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            RightImages.Add(rbmp);
            LeftImages.Add(lbmp);
            WavPlayer.Play();
            this.Invoke(StatusUpdate, new Object[] { "Processing (" + (LeftImages.Count - 1) + " in que)" });
            return true;
        }
        /// <summary>
        /// Image Capture modes
        /// </summary>
        enum CaptureModes
        {
            SingleShot,
            Sequence,
            Timer
        }

        #endregion

        #region Image Processing Subroutine

        /// <summary>
        /// Used by status updater
        /// </summary>
        /// <param name="str"></param>
        private delegate void StringHandler(string str);
        /// <summary>
        /// Actually used for status updater only
        /// </summary>
        /// <param name="progress"></param>
        private delegate void IntHandler(int progress);
        /// <summary>
        /// Update the 3D stereo preview picturebox
        /// </summary>
        /// <param name="StereoImage"></param>
        private delegate void ImageHandler(Image StereoImage);

        /// <summary>
        /// Image list to process
        /// </summary>
        public static List<Bitmap> LeftImages;
        public static List<Bitmap> RightImages;

        /// <summary>
        /// The flag to say if the processor is busy or not
        /// </summary>
        bool isBusy = false;

        /// <summary>
        /// Shuting the application down
        /// </summary>
        bool ShutingDown = false;

        /// <summary>
        /// This method will be called on a seperate thread to
        /// process stereoscopic images to create a Red Cyan 3D Image
        /// </summary>
        /// <returns></returns>
        private void StartProcessingLoop()
        {
            StringHandler StatusUpdate = new StringHandler(UpdateStatus);
            while (true)
            {
                if (ShutingDown) return;

                if (LeftImages.Count == 0)
                {
                    isBusy = false;
                    try { this.Invoke(StatusUpdate, new Object[] { "Ready" }); }
                    catch { return; }
                    System.Threading.Thread.Sleep(100);
                    continue;
                }
                isBusy = true;
                this.Invoke(StatusUpdate, new Object[] { "Processing (" + (LeftImages.Count - 1) + " in que)" });
                //Take the first image in que for processing
                Bitmap LeftImage = LeftImages[0];
                Bitmap RightImage = RightImages[0];
                if (ProcessStereo(LeftImage, RightImage))
                {
                    LeftImages.Remove(LeftImages[0]);
                    RightImages.Remove(RightImages[0]);
                    LeftImage.Dispose();
                    RightImage.Dispose();
                }
                else
                {
                    if (ShutingDown) return;
                    StringHandler DialogueHandler = new StringHandler(ShowDialogue);
                    //The path is not assigned may be, so lets check
                    string ImagePath = Properties.Settings.Default.ImageDirectory;
                    if (ImagePath == "")
                    {
                        this.Invoke(DialogueHandler, new Object[] { "Please set the image directory and then click to start processor again" });
                        return;
                    }
                    if (!System.IO.Directory.Exists(ImagePath))
                    {
                        this.Invoke(DialogueHandler, new Object[] { "The selected directory doesn't exist and then click to start processor again" });
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// Update status label
        /// </summary>
        /// <param name="status"></param>
        private void UpdateStatus(string status)
        {
            try
            {
                StatusL.Text = status;
            }
            catch { }
        }
        /// <summary>
        /// Update status progressbar
        /// </summary>
        /// <param name="Progress"></param>
        private void UpdateProgress(int Progress)
        {
            try
            {
                if (Progress > 100) Progress = 100;
                StatusP.Value = Progress;
                if (Progress == 100) StatusP.Visible = false; else StatusP.Visible = true;
            }
            catch { }
        }
        /// <summary>
        /// Show a dialogue box with the message
        /// </summary>
        /// <param name="msg"></param>
        private void ShowDialogue(string msg)
        {
            MessageBox.Show(msg, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
            StartProcessor.Enabled = true;
        }
        /// <summary>
        /// Update the Stereo Preview window
        /// </summary>
        /// <param name="StereoImage"></param>
        private void UpdateStereoImage(Image StereoImage)
        {
            try
            {
                StereoPreviewWindow.Image = StereoImage;
            }
            catch { }
        }

        /// <summary>
        /// Main processor that takes images to process and save the images
        /// </summary>
        /// <param name="leftImage"></param>
        /// <param name="rightImage"></param>
        /// <returns></returns>
        private bool ProcessStereo(Bitmap leftImage, Bitmap rightImage)
        {
            string ImagePath = Properties.Settings.Default.ImageDirectory;
            if (ImagePath == "")
                return false; //I haven't found the directory to save the image so stopping imediately and returning false
            if (!System.IO.Directory.Exists(ImagePath)) return false;
            if (ImagePath[ImagePath.Length - 1] != '\\') ImagePath = ImagePath + "\\";

            long num=1;
            string IP = ImagePath + "Image3D " + num + ".png";
            while (System.IO.File.Exists(IP))
            {
                if (ShutingDown) return false;
                num++;
                IP = ImagePath + "Image3D " + num + ".png";
            }
            ImagePath = IP;

            long total = leftImage.Width * leftImage.Height;
            long tillNow = 0;
            int Progress = 0;

            //I have killed this progress handler
            //IntHandler ProgressHandler = new IntHandler(UpdateProgress);

            Bitmap FBmp = new Bitmap(rightImage.Width, leftImage.Height);
            UnsafeBitmap OutputBitmap = new UnsafeBitmap(FBmp);
            UnsafeBitmap LeftUnsafe = new UnsafeBitmap(leftImage);
            UnsafeBitmap RightUnsafe = new UnsafeBitmap(rightImage);
            OutputBitmap.LockBitmap();
            LeftUnsafe.LockBitmap();
            RightUnsafe.LockBitmap();

            for (int w = 0; w < FBmp.Width; w++)
            {
                for (int h = 0; h < FBmp.Height; h++)
                {
                    if (ShutingDown) return false;
                    OutputBitmap.SetPixel(w, h, new Pixel(RightUnsafe.GetPixel(w, h).red, LeftUnsafe.GetPixel(w, h).green, LeftUnsafe.GetPixel(w, h).blue));
                    tillNow++;
                    Progress = (int)(((Double)tillNow / (Double)total) * 100);
                }
                //This evil invoking was taking up all times :@
                // this.Invoke(ProgressHandler, new Object[] { Progress });
            }

            OutputBitmap.UnlockBitmap();
            LeftUnsafe.UnlockBitmap();
            RightUnsafe.UnlockBitmap();

            FBmp = OutputBitmap.Bitmap;

            ImageHandler StereoHandler = new ImageHandler(UpdateStereoImage);
            //UI invoking takes time but I can not avoid this invoking
            this.Invoke(StereoHandler, new Object[] { FBmp.Clone() });
            try
            {
                FBmp.Save(ImagePath, System.Drawing.Imaging.ImageFormat.Png);
            }
            catch
            {
                return false;
            }
            FBmp.Dispose();
            return true;
        }

        #endregion

        #region Real Time Preview Subroutine

        bool IsRealTimeOn = false;
        bool StopRealTime = false;
        //######BETA METHOD######
        /// <summary>
        /// This method will update the 3D preview display in real time
        /// </summary>
        private void RealTimeShow()
        {
            Bitmap lbmp = null, rbmp = null;
            ImageHandler StereoHandler = new ImageHandler(UpdateStereoImage);
            while (!ShutingDown)
            {
                IsRealTimeOn = true;
                try
                {
                    lbmp = LeftCamWindow.GetCurrentVideoFrame();
                    rbmp = RightCamWindow.GetCurrentVideoFrame();
                }
                catch { Thread.Sleep(100); continue; }

                if (lbmp == null || rbmp == null) { Thread.Sleep(100); continue; }

                Bitmap FBmp = new Bitmap(rbmp.Width, lbmp.Height);
                UnsafeBitmap OutputBitmap = new UnsafeBitmap(FBmp);
                UnsafeBitmap LeftUnsafe = new UnsafeBitmap(lbmp);
                UnsafeBitmap RightUnsafe = new UnsafeBitmap(rbmp);
                OutputBitmap.LockBitmap();
                LeftUnsafe.LockBitmap();
                RightUnsafe.LockBitmap();

                for (int w = 0; w < FBmp.Width; w++)
                {
                    for (int h = 0; h < FBmp.Height; h++)
                    {
                        if (ShutingDown) { IsRealTimeOn = false; return; }
                        OutputBitmap.SetPixel(w, h, new Pixel(RightUnsafe.GetPixel(w, h).red, LeftUnsafe.GetPixel(w, h).green, LeftUnsafe.GetPixel(w, h).blue));
                    }
                }

                OutputBitmap.UnlockBitmap();
                LeftUnsafe.UnlockBitmap();
                RightUnsafe.UnlockBitmap();

                FBmp = OutputBitmap.Bitmap;
                try
                {
                    this.Invoke(StereoHandler, new Object[] { FBmp.Clone() });
                    if (StopRealTime) { IsRealTimeOn = false; return; }
                }
                catch { return; }
            }
        }
        
        #endregion

        /// <summary>
        /// If processor is down then start the processor menually
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void StartProcessor_Click(object sender, EventArgs e)
        {
            StartProcessor.Enabled = false;
            Thread Th = new Thread(StartProcessingLoop);
            Th.Start();
        }

        /// <summary>
        /// Form has started
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_Load(object sender, EventArgs e)
        {
            CaptureMode = CaptureModes.SingleShot;
            SIntervel.Enabled = false;
            SDuration.Enabled = false;
            TWait.Enabled = false;
            SingleShotTab.Checked = true;
            SequenceTab.Checked = false;
            TimerTab.Checked = false;
            LocationLabel.Text = Properties.Settings.Default.ImageDirectory;
            LeftImages = new List<Bitmap>();
            RightImages = new List<Bitmap>();
            StartProcessor.Enabled = false;
            Thread Th = new Thread(StartProcessingLoop);
            Th.Start();
        }
        /// <summary>
        /// Close the threads
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (isBusy)
            {
                DialogResult dr = MessageBox.Show("There are " + LeftImages.Count + " images in que to be processed. If you exit the application, you will lose all unprocessed images. Do you really want to exit?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Information);
                if (dr == System.Windows.Forms.DialogResult.No)
                {
                    e.Cancel = true;
                    return;
                }
            }
            ShutingDown = true;
            StopCameras();
        }
        /// <summary>
        /// Switch cameras
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CamCombo_SelectedIndexChanged(object sender, EventArgs e)
        {
            StopCameras();
            StartCameras();
        }
        /// <summary>
        /// Set the location to save files
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BrowseButton_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            if (LocationLabel.Text != "")
            {
                fbd.SelectedPath = LocationLabel.Text;
            }

            System.Windows.Forms.DialogResult dr = fbd.ShowDialog();

            if (dr == System.Windows.Forms.DialogResult.OK)
            {
                Properties.Settings.Default.ImageDirectory = fbd.SelectedPath;
                Properties.Settings.Default.Save();
                LocationLabel.Text = Properties.Settings.Default.ImageDirectory;
            }
            fbd.Dispose();
        }
        bool isCancle;
        /// <summary>
        /// Capture a Image
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CaptureImage_Click(object sender, EventArgs e)
        {
            if (CaptureImage.Text == "Cancle")
            { isCancle = true; return; } isCancle = false;
            switch (CaptureMode)
            {
                case CaptureModes.SingleShot:
                    Capture();
                    break;
                case CaptureModes.Timer:
                    CaptureImage.Text = "Cancle";
                    Thread th1 = new Thread(new ParameterizedThreadStart(TimerCapture));
                    th1.Start((int)TWait.Value);
                    break;
                case CaptureModes.Sequence:
                    if (SDuration.Value <= SIntervel.Value)
                    {
                        MessageBox.Show("Capture duration should be greater than the capture intervel", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                        return;
                    }
                    CaptureImage.Text = "Cancle";
                    Thread th2 = new Thread(new ParameterizedThreadStart(SequenceCapture));
                    th2.Start(new Object[] { (int)SIntervel.Value, (int)SDuration.Value });
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// If checked is changed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Tab_CheckedChanged(object sender, EventArgs e)
        {
            RadioButton Sender = (RadioButton)sender;

            if (Sender.Name == "SingleShotTab")
            {
                if (Sender.Checked)
                {
                    CaptureMode = CaptureModes.SingleShot;
                    SIntervel.Enabled = false;
                    SDuration.Enabled = false;
                    TWait.Enabled = false;
                    SequenceTab.Checked = false;
                    TimerTab.Checked = false;
                }
            }
            else if (Sender.Name == "SequenceTab")
            {
                if (Sender.Checked)
                {
                    CaptureMode = CaptureModes.Sequence;
                    SIntervel.Enabled = true;
                    SDuration.Enabled = true;
                    TWait.Enabled = false;
                    SingleShotTab.Checked = false;
                    TimerTab.Checked = false;
                }
            }
            else if (Sender.Name == "TimerTab")
            {
                if (Sender.Checked)
                {
                    CaptureMode = CaptureModes.Timer;
                    SIntervel.Enabled = false;
                    SDuration.Enabled = false;
                    TWait.Enabled = true;
                    SingleShotTab.Checked = false;
                    SequenceTab.Checked = false;
                }
            }
        }
        /// <summary>
        /// Show tool tip
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FeedbackButton_MouseHover(object sender, EventArgs e)
        {
            ToolTips.Show("Feedback", FeedbackButton);
        }
        /// <summary>
        /// Promot to feedback
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FeedbackButton_Click(object sender, EventArgs e)
        {
            Feedback fback = new Feedback();
            fback.ShowDialog();
        }

        /// <summary
        /// Handle menual image processing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mAdd_Click(object sender, EventArgs e)
        {
            Add_Menually am = new Add_Menually();
            am.ShowDialog(this);
            am.Dispose();
        }

        private void FeedbackButton_MouseLeave(object sender, EventArgs e)
        {

        }

        /// <summary>
        /// Start real time preview
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void StartRealTimePreview_Click(object sender, EventArgs e)
        {
            if (IsRealTimeOn)
            {
                StopRealTime = true;
                StartRealTimePreview.Image = Properties.Resources.real_time_off;
                ToolTips.SetToolTip(StartRealTimePreview, "Start Real-time Preview");
                return;
            }
            else
            {
                StopRealTime = false;
                Thread th = new Thread(RealTimeShow);
                th.Start();
                StartRealTimePreview.Image = Properties.Resources.real_time_on;
                ToolTips.SetToolTip(StartRealTimePreview, "Stop Real-time Preview");
                return;
            }
        }

    }
}
